package timing.ukulele.storage.controller.minio;

import io.minio.Result;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import timing.ukulele.common.data.ResponseData;
import timing.ukulele.storage.service.minio.ObjectService;
import timing.ukulele.web.controller.BaseController;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 存储对象相关操纵接口
 */
@RestController
@RequestMapping("/object")
@Tag(name = "存储对象相关操纵接口")
@Slf4j
public class ObjectController extends BaseController {
    private final ObjectService objectService;

    public ObjectController(ObjectService objectService) {
        this.objectService = objectService;
    }

    /**
     * 将流数据返回
     *
     * @param response 请求响应对象
     * @param is       输入流
     * @throws IOException IO异常
     */
    private void handleOutStream(HttpServletResponse response, InputStream is, String fileName) throws IOException {
        response.setHeader("Content-type", "application/octet-stream");
        response.setContentType("application/octet-stream");
        response.setHeader("Content-Disposition", "attachment;fileName=" + fileName);
        ServletOutputStream outputStream = response.getOutputStream();
        // 读取输入流直到EOF并打印到控制台。
        byte[] buf = new byte[1024];
        int i = is.read(buf);
        while (i != -1) {
            outputStream.write(buf, 0, i);
            i = is.read(buf);
        }
    }

    @GetMapping("/get")
    @Operation(summary = "以流的形式下载一个对象")
    public String getObject(@Parameter(description = "存储桶名称", required = true, name = "bucketName", in = ParameterIn.QUERY) @RequestParam("bucketName") String bucketName,
                            @Parameter(description = "存储桶里的对象名称", required = true, name = "objectName", in = ParameterIn.QUERY) @RequestParam("objectName") String objectName,
                            HttpServletResponse response) {
        try (InputStream is = this.objectService.getObject(bucketName, objectName)) {
            handleOutStream(response, is, objectName);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return "";
    }

    @GetMapping("/get/resume")
    @Operation(summary = "下载对象指定区域的字节数组做为流。（断点下载）")
    public String getObject(@Parameter(description = "存储桶名称", required = true, name = "bucketName", in = ParameterIn.QUERY) @RequestParam("bucketName") String bucketName,
                            @Parameter(description = "存储桶里的对象名称", required = true, name = "objectName", in = ParameterIn.QUERY) @RequestParam("objectName") String objectName,
                            @Parameter(description = "是起始字节的位置", required = true, example = "1024", name = "offset", in = ParameterIn.QUERY) @RequestParam(value = "offset") Long offset,
                            @Parameter(description = "要读取的长度 (可选，如果无值则代表读到文件结尾)", example = "1024", name = "length", in = ParameterIn.QUERY) @RequestParam(value = "length", required = false) Long length,
                            HttpServletResponse response) {
        try (InputStream is = this.objectService.getObject(bucketName, objectName, offset, length)) {
            handleOutStream(response, is, objectName);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return "";
    }

    // TODO 密钥如何获取
    @GetMapping("/get/secret")
    @Operation(summary = "以流的形式下载一个加密对象")
    public String getObjectSecret(@Parameter(description = "存储桶名称", required = true, name = "bucketName", in = ParameterIn.QUERY) @RequestParam("bucketName") String bucketName,
                                  @Parameter(description = "存储桶里的对象名称", required = true, name = "objectName", in = ParameterIn.QUERY) @RequestParam("objectName") String objectName,
                                  HttpServletResponse response) {
        //生成256位AES key。
        KeyGenerator symKeyGenerator;
        try {
            symKeyGenerator = KeyGenerator.getInstance("AES");
        } catch (NoSuchAlgorithmException e) {
            log.error(e.getMessage());
            return "生成密钥失败";
        }
        symKeyGenerator.init(256);
        SecretKey symKey = symKeyGenerator.generateKey();

        try (InputStream is = this.objectService.getObject(bucketName, objectName, symKey)) {
            handleOutStream(response, is, objectName);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return "";
    }

    @PostMapping("/put")
    @Operation(summary = "上传对象。单个对象的最大大小限制在5TB。putObject在对象大于5MiB时，自动使用multiple parts方式上传。这样，当上传失败时，客户端只需要上传未成功的部分即可（类似断点上传）。上传的对象使用MD5SUM签名进行完整性验证。")
    public ResponseData<String> putObject(
            @Parameter(description = "存储桶名称", required = true, name = "bucketName", in = ParameterIn.QUERY) @RequestParam("bucketName") String bucketName,
            @Parameter(description = "存储桶里的对象名称", required = true, name = "objectName", in = ParameterIn.QUERY) @RequestParam("objectName") String objectName,
            @Parameter(description = "Content type", required = true, name = "contentType", in = ParameterIn.QUERY) @RequestParam("contentType") String contentType,
            @Parameter(description = "文件", name = "uploadFile") @RequestParam("uploadFile") MultipartFile file) {
        try {
            this.objectService.putObject(bucketName, objectName, file.getInputStream(), contentType);
            return successResponse();
        } catch (Exception e) {
            log.error(e.getMessage());
            return failResponse(e.getMessage());
        }
    }

    @PostMapping("/put/secret")
    @Operation(summary = "上传对象。加密存储")
    public ResponseData<String> putObjectSecret(
            @Parameter(description = "存储桶名称", required = true, name = "bucketName", in = ParameterIn.QUERY) @RequestParam("bucketName") String bucketName,
            @Parameter(description = "存储桶里的对象名称", required = true, name = "objectName", in = ParameterIn.QUERY) @RequestParam("objectName") String objectName,
            @Parameter(description = "Content type", required = true, name = "contentType", in = ParameterIn.QUERY) @RequestParam("contentType") String contentType,
            @Parameter(description = "文件", name = "uploadFile") @RequestParam("uploadFile") MultipartFile file) {
        try {
            //生成256位AES key.
            KeyGenerator symKeyGenerator = KeyGenerator.getInstance("AES");
            symKeyGenerator.init(256);
            SecretKey symKey = symKeyGenerator.generateKey();
            this.objectService.putObject(bucketName, objectName, file.getInputStream(), contentType, symKey);
            return successResponse();
        } catch (Exception e) {
            log.error(e.getMessage());
            return failResponse(e.getMessage());
        }
    }

    @DeleteMapping("/remove/{bucketName}")
    @Operation(summary = "删除多个对象")
    public ResponseData<List<String>> removeObject(
            @Parameter(description = "存储桶名称", required = true, name = "bucketName", in = ParameterIn.PATH) @PathVariable("bucketName") String bucketName,
            @Parameter(description = "含有要删除的多个object名称的迭代器对象", required = true, name = "objectNames") @RequestBody List<String> objectNames) {
        List<DeleteObject> objectList = new ArrayList<>(objectNames.size());
        objectNames.forEach(item -> {
            DeleteObject object = new DeleteObject(item);
            objectList.add(object);
        });
        final Iterable<Result<DeleteError>> results = this.objectService.removeObject(bucketName, objectList);
        List<String> list = null;
        if (results != null) {
            list = new ArrayList<>();
            Iterator<Result<DeleteError>> it = results.iterator();
            try {
                while (it.hasNext()) {
                    if (it.next() != null && it.next().get() != null)
                        list.add(it.next().get().message());
                }
            } catch (Exception e) {
                log.error(e.getMessage());
                return failResponse();
            }
        }
        return successResponse(list);
    }
}
